/*
 * @Author: LVGRAPE
 * @LastEditors: LVGRAPE
 */
#include "bootloader.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifndef NULL
#define NULL (void *)0
#endif

#define CRC32_INIT_VALUE 0xFFFFFFFF
#define CRC32_XOR_VALUE 0xFFFFFFFF

extern void delay_ms(uint32_t ms);
extern uint32_t crc32_cal(const uint8_t *pdata, int32_t len, uint32_t initial, uint32_t finalXor, bool inputReflected, bool resultReflected);
int dev_sync(bl_info_t *bl)
{
    int sendtry = 0;
    printf("\nTry to sync ");
    fflush(stdout);
    while (1)
    {
        bl_pack_send(bl, CMD_SYNC, 0, NULL, 0);
        // printf("\nsending:");
        // for (int i = 0; i < bl->pack.buffer_size; i++)
        // {
        //     if (i % 16 == 0 && i != 0)
        //         printf("\n");
        //     printf("%02X ", bl->pack.buffer[i]);
        // }
        // printf("\n");
        delay_ms(1);
        if (bl_pack_recv(bl) == 0 && bl->pack.cmd1 == CMD_ACK)
        {
            printf("Sync success!\n");
            // break;
            return 0;
        }
        else
        {
            // printf("Sync failed!\n");
            delay_ms(10);
            sendtry++;
            if (sendtry >= 10)
            {
                printf("sync timeout! exit!\n");
                return -2;
            }
        }
        printf(".");
        fflush(stdout);
        // printf("\nrev: %d", bl->pack.buffer_size);
        // for (int i = 0; i < bl->pack.buffer_size; i++)
        // {
        //     if (i % 16 == 0)
        //         printf("\n");
        //     printf("%02X ", bl->pack.buffer[i]);
        // }
        // printf("\n");
    }
    printf("\n");
    return -1;
}
int getVersion(bl_info_t *bl)
{
    printf("\nget version\n");
    int sendtry = 0;
    while (1)
    {

        bl_pack_send(bl, CMD_VERSION, 0, NULL, 0);
        // printf("\nsending:");
        // for (int i = 0; i < bl->pack.buffer_size; i++)
        // {
        //     if (i % 16 == 0)
        //         printf("\n");
        //     printf("%02X ", bl->pack.buffer[i]);
        // }
        // printf("\n");
        delay_ms(1);
        if (bl_pack_recv(bl) == 0 && bl->pack.cmd1 == CMD_ACK)
        {
            if (bl->pack.cmd1 == CMD_ACK)
            {
                printf("get version success\n");
                memcpy(&bl->version, bl->pack.data, bl->pack.data_size);
                printf("version:0x%08X\n", bl->version);
                return 0;
            }
        }
        delay_ms(1);
        if (sendtry++ >= 3)
        {
            printf("get version timeout! exit!\n");
            return -1;
        }
        printf(".");
    }
    printf("\n");
}
int getId(bl_info_t *bl)
{
    printf("\nget id...\n");
    int sendtry = 0;
    while (1)
    {

        bl_pack_send(bl, CMD_GETID, 0, NULL, 0);
        // printf("\nsending:");
        // for (int i = 0; i < bl->pack.buffer_size; i++)
        // {
        //     if (i % 16 == 0)
        //         printf("\n");
        //     printf("%02X ", bl->pack.buffer[i]);
        // }
        // printf("\n");
        delay_ms(1);
        if (bl_pack_recv(bl) == 0 && bl->pack.cmd1 == CMD_ACK)
        {
            if (bl->pack.cmd1 == CMD_ACK)
            {
                printf("get id success\n");
                memcpy(&bl->DeviceID, bl->pack.data, bl->pack.data_size);
                printf("DeviceID: [%016llX]\n\n", bl->DeviceID);

                return 0;
            }
        }
        delay_ms(1);
        if (sendtry++ >= 3)
        {
            printf("get version timeout! exit!\n");
            return -1;
        }
        printf(".");
    }
    printf("\n\n");
    return -1;
}
int get_fwInfo(bl_info_t *bl)
{
    printf("get fwInfo...\n");
    int sendtry = 0;
    while (1)
    {

        bl_pack_send(bl, CMD_R_FW_INFO, 0, NULL, 0);
        // printf("\nSending:");
        // for (int i = 0; i < bl->pack.buffer_size; i++)
        // {
        //     if (i % 16 == 0)
        //         printf("\n");
        //     printf("%02X ", bl->pack.buffer[i]);
        // }
        // printf("\n");
        delay_ms(10);
        uint32_t ret = bl_pack_recv(bl);
        if (ret == 0)
        {
            if (bl->pack.cmd1 == CMD_ACK && bl->pack.data_size == sizeof(firmware_info_t))
            {
                firmware_info_t onchipFW;

                printf("get fwInfo success:crc 0x%08X\n", bl->pack.crc);
                memcpy(&onchipFW, bl->pack.data, sizeof(firmware_info_t));
                uint32_t iCrc = crc32_cal((uint8_t *)&onchipFW + 4, sizeof(firmware_info_t) - 4, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
                if (iCrc == onchipFW.infoCrc)
                {
                    int b3, b2, b1, b0;
                    printf("get onchip fwInfo:\n");
                    printf("name: %s\n", onchipFW.name);
                    b3 = onchipFW.version >> 24;
                    b2 = (onchipFW.version >> 16) & 0xFF;
                    b1 = (onchipFW.version >> 8) & 0xFF;
                    b0 = onchipFW.version & 0xFF;
                    printf("version: V%d.%d.%d.%d\n", b3, b2, b1, b0);
                    b3 = onchipFW.pid >> 24;
                    b2 = (onchipFW.pid >> 16) & 0xFF;
                    b1 = (onchipFW.pid >> 8) & 0xFF;
                    b0 = onchipFW.pid & 0xFF;
                    printf("pid: %d,%d,%d,%d\n", b3, b2, b1, b0);
                    int y, m, d;
                    y = onchipFW.date >> 16;
                    m = (onchipFW.date >> 8) & 0xFF;
                    d = onchipFW.date & 0xFF;
                    printf("date: %d-%d-%d\n", y, m, d);
                    printf("bootTimes:%d\n\n", onchipFW.bootTimes);

                    memcpy(&bl->fw_info, &onchipFW, sizeof(firmware_info_t));
                }
                else
                {
                    printf("iCrc error! 0x%08X,%08X\n", iCrc, onchipFW.infoCrc);
                    printf("onchip fw empty!\n");
                }
                // for(int i=0;i<bl->pack.data_size;i++)
                // {
                //     if(i%16==0)printf("\n");
                //     printf("%02X ",bl->pack.data[i]);
                // }
                return 0;
            }
            else
            {
                printf("get fwInfo error! cmd1:0x%02X, data_size:%d\n", bl->pack.cmd1, bl->pack.data_size);
            }
        }
        printf("\n reving: %d,0x%08X\n", bl->pack.buffer_size, ret);
        for (int i = 0; i < bl->pack.buffer_size; i++)
        {
            if (i % 16 == 0)
                printf("\n");
            printf("%02X ", bl->pack.buffer[i]);
        }
        printf("\n");
        delay_ms(1);
        if (sendtry++ >= 1)
        {
            printf("get fwInfo timeout! exit!\n");
            return -1;
        }
        printf(".");
    }
    printf("\n");
    return -2;
}
int erase_flash(bl_info_t *bl, firmware_info_t *firmwareInfo)
{
#if 0
    printf("\n\nerase flash...\n");
    int sendtry = 0;
    uint32_t erase_size = firmware.size;
    uint32_t erase_addr = firmware.start_addr;
    // printf("erase addr: 0x%08X, erase size: %d sectorSize:%d\n", erase_addr, erase_size, bl->sectorSize);
    uint32_t sectorCount = erase_size / bl->sectorSize;
    sectorCount += ((erase_size % bl->sectorSize) ? 1 : 0);
    printf("erase addr: 0x%08X, erase size: %d, sector count: %d %d\n", erase_addr, erase_size, sectorCount, bl->sectorSize);

    while (1)
    {
        tcflush(f_com, TCIOFLUSH);
        bl_pack_send(bl, CMD_ERASE, erase_addr, (uint8_t *)&erase_size, 4);

        // printf("Sending:");
        // for (int i = 0; i < bl->pack.buffer_size; i++)
        // {
        //     if (i % 16 == 0)
        //         printf("\n");
        //     printf("%02X ", bl->pack.buffer[i]);
        // }
        // printf("\n");

        uint32_t endAddr = 0;
        int waittry = 0;

        printf("erasing...\n");
        for (int i = 0; i <= sectorCount; i++)
        {
            printf("=");
        }
        printf("\n");

        while (1)
        {
            delay_ms(10);
            uint32_t ret = bl_pack_recv(bl);
            if (ret == 0 && bl->pack.cmd1 == CMD_ACK)
            {
                endAddr = bl->pack.data[0] | (bl->pack.data[1] << 8) | (bl->pack.data[2] << 16) | (bl->pack.data[3] << 24);

                printf("#");
                fflush(stdout);
                // delay_ms(1);
                // printf("erase addr: 0x%08X, endAddr: 0x%08X\n", bl->pack.addr, endAddr);
                if (bl->pack.addr >= endAddr)
                {

                    printf("\n");
                    for (int i = 0; i <= sectorCount; i++)
                    {
                        printf("=");
                    }
                    printf("\n");
                    printf("erase success!\n\n");
                    return 0;
                }
                waittry = 0;
            }
            else
            {
                if (waittry++ > 10)
                {
                    printf("eraser error at 0x%08X\n", bl->pack.addr);
                    return -1;
                }
            }
        }
        if (sendtry++ >= 2)
        {
            printf("erase timeout! exit!\n");
            return -2;
        }
    }
#endif
    printf("\n\nerase flash...\n");
    int sendtry = 0;
    uint32_t erase_size = bl->sectorSize;
    uint32_t erase_addr = firmwareInfo->start_addr;
    // printf("erase addr: 0x%08X, erase size: %d sectorSize:%d\n", erase_addr, erase_size, bl->sectorSize);
    uint32_t sectorCount = firmwareInfo->size / bl->sectorSize;
    printf("addr: 0x%08X, erase size: %d, sector count: %d sector size:%d\n", erase_addr, erase_size, sectorCount, bl->sectorSize);
    for (int i = 0; i <= sectorCount; i++)
    {
        printf("=");
    }
    printf("\n");
    for (uint32_t eAddr = firmwareInfo->start_addr; eAddr < firmwareInfo->start_addr + firmwareInfo->size; eAddr += bl->sectorSize)
    {
        bl_pack_send(bl, CMD_ERASE, eAddr, (uint8_t *)&erase_size, 4);
        delay_ms(10);
        int ret = bl_pack_recv(bl);

        // bl->pack.buffer_size = read(f_com,bl->pack.buffer,20);
        // ret = bl_pack_recv(bl);
        // int ret = 0;
        if (ret == 0 && bl->pack.cmd1 == CMD_ACK)
        {
            printf("#");
            fflush(stdout);
        }
        else
        {
            printf("erase error at 0x%08X, ret=%d\n", eAddr, ret);
            printf("rx:[ ");
            bl->pack.buffer_size = bl->pack.buffer_size > bl->sectorSize ? bl->sectorSize : bl->pack.buffer_size;
            for (int i = 0; i < bl->pack.buffer_size; i++)
            {
                printf("%02X ", bl->pack.buffer[i]);
            }
            printf(" ]\n");
            return -1;
        }
    }
    printf("\n");
    for (int i = 0; i <= sectorCount; i++)
    {
        printf("=");
    }
    printf("\n");
    printf("erase done!\n\n");
    return 0;
}
int write_firmware(bl_info_t *bl, firmware_info_t *firmwareInfo, uint8_t *firmware_data)
{

    uint32_t write_already = 0;
    uint32_t write_remain = firmwareInfo->size;
    uint32_t write_addr = firmwareInfo->start_addr;
    uint32_t write_size = 0;
    uint32_t sendtry = 0;

    uint32_t stepPercent = 0;
    uint32_t stepPercentPre = 0;
    uint32_t packSize = bl->sectorSize > 2048 ? 2048 : bl->sectorSize;
    uint32_t stepMax = (firmwareInfo->size) / packSize;
    stepMax = stepMax > 100 ? 100 : stepMax;
    printf("programming...\n");
    for (int i = 0; i <= stepMax; i++)
    {
        printf("=");
    }
    printf("\n");
    while (1)
    {
        write_size = write_remain > packSize ? packSize : write_remain;
        bl_pack_send(bl, CMD_WRITE, write_addr, &firmware_data[write_already], write_size);
        int ret = bl_pack_recv(bl);
        // int waitry = 0;
        // while (ret != 0)
        // {
        //     delay_ms(1);
        //     ret = bl_pack_recv(bl);
        //     if (waitry++ > 3)
        //     {
        //         printf("write error [%08X] at 0x%08X, crc error\n", ret, write_addr);
        //         return -1;
        //     }
        // }
        // delay_ms(1);
        if (ret == 0 && bl->pack.cmd1 == CMD_ACK)
        {
            sendtry = 0;
            write_already += write_size;
            write_remain -= write_size;
            write_addr += write_size;

            stepPercent = (write_already * 100) / firmwareInfo->size;
            if (stepPercent != stepPercentPre)
            {
                stepPercentPre = stepPercent;
                printf(">");
                // printf(">%d%%\n",stepPercent);
                fflush(stdout);
            }
            // printf("write addr: 0x%08X, write_size: %d write_remain:%d\n", write_addr, write_size, write_remain);

            if (write_remain == 0)
            {
                printf("\n");
                for (int i = 0; i <= stepMax; i++)
                {
                    printf("=");
                }
                printf("\n");
                printf("Done!\n\n");
                return 0;
            }
        }
        else
        {
            // printf("write error [%08X] at 0x%08X\n rev: [%d] \n", ret, write_addr, bl->pack.buffer_size);
            // for (int i = 0; i < bl->pack.buffer_size; i++)
            // {
            //     printf("0x%02X ", bl->pack.buffer[i]);
            // }
            // printf("\n");
            if (sendtry++ >= 10)
            {
                printf("write error [%08X] at 0x%08X\n rev: [%d] \n", ret, write_addr, bl->pack.buffer_size);
                printf("write timeout! exit!\n");
                return -1;
            }
        }
    }
    return -2;
}
int read_firmware(bl_info_t *bl, firmware_info_t *firmwareInfo, char *outFilename)
{
    uint32_t read_total = 0;
    uint32_t read_size = 0;
    uint32_t sendtry = 0;
    uint32_t read_addr = firmwareInfo->start_addr;
    uint32_t read_remain = firmwareInfo->size;
    uint8_t *firmwareRead = malloc(firmwareInfo->size);
    if (!firmwareRead)
    {
        printf("malloc error\n");
        return -1;
    }
    uint32_t packCount = firmwareInfo->size / bl->sectorSize;
    packCount = packCount > 100 ? 100 : packCount;
    uint32_t curPack = 0, prePack = 0;
    printf("Verifying...\n");
    for (int i = 0; i <= packCount; i++)
    {
        printf("=");
        // fflush(stdout);
    }
    printf("\n");
    while (1)
    {
        read_size = read_remain > bl->sectorSize ? bl->sectorSize : read_remain;
        bl_pack_send(bl, CMD_READ, read_addr, (uint8_t *)&read_size, 4);

        int waitting = 0;
        while (1) // wait ack
        {
            // delay_ms(2);
            int ret = bl_pack_recv(bl);
            if (ret == 0 && bl->pack.cmd1 == CMD_ACK)
            {
                memcpy(&firmwareRead[read_total], bl->pack.data, bl->pack.data_size);
                // printf("read addr: 0x%08X, read_size: %d\n", read_addr, read_size);
                if (read_size != bl->pack.data_size)
                {
                    printf("read error at 0x%08X! wanted[%d] get[%d]\n", read_addr, read_size, bl->pack.data_size);
                }
                read_total += read_size;
                read_remain -= read_size;
                read_addr += read_size;
                sendtry = 0;

                curPack = read_total * 100 / firmwareInfo->size;
                if (curPack != prePack)
                {
                    prePack = curPack;
                    printf("<");
                    fflush(stdout);
                }
                break;
            }
            else
            {
                // printf("rev: 0x%08X, [%d]", ret, bl->pack.buffer_size);
                // for (int i = 0; i < bl->pack.buffer_size; i++)
                // {
                //     if (i % 32 == 0)
                //         printf("\n");
                //     printf("%02X ", bl->pack.buffer[i]);
                // }
                // printf("\n");
                if (waitting++ > 2)
                {
                    break;
                    // printf("\nread timeout! exit! at: 0x%08X\n", read_addr);
                    // return -2;
                }
            }
        }
        if (read_remain == 0)
        {
            printf("\n");
            for (int i = 0; i <= packCount; i++)
            {
                printf("=");
                // fflush(stdout);
            }
            printf("\n");
            printf("done!\n");
            break;
        }
        if (sendtry++ >= 3)
        {
            printf("read timeout! exit!\n");
            return -1;
        }
    }
    if (outFilename != 0)
    {
        FILE *f_read = fopen(outFilename, "wb");
        if (!f_read)
        {
            printf("open file error!\n");
        }

        size_t writeSize = fwrite(firmwareRead, 1, firmwareInfo->size, f_read);
        if (writeSize != firmwareInfo->size)
        {
            printf("write file error! %d,%d\n", writeSize, firmwareInfo->size);
        }
        fclose(f_read);
    }
    uint32_t crc = crc32_cal(firmwareRead, firmwareInfo->size, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
    printf("read crc: 0x%08X <--> 0x%08X\n", crc, firmwareInfo->FWcrc);
    if (crc != firmwareInfo->FWcrc)
    {
        printf("crc error! read crc: 0x%08X, firmware crc: 0x%08X\n", crc, firmwareInfo->FWcrc);
        // for (size_t i = 0; i < firmwareInfo->size; i++)
        // {
        //     if (firmware_data[i] != firmwareRead[i])
        //     {
        //         printf("read error at 0x%08X, I[%02X] O[%02X]\n", i, firmware_data[i], firmwareRead[i]);
        //         return -1;
        //     }
        // }
        return -1;
    }
    free(firmwareRead);
    printf("crc ok!\n");
    return 0;
}
int write_fwInfo(bl_info_t *bl, firmware_info_t *firmwareInfo)
{
    uint32_t sendtry = 0;
    printf("\nUpdating firmware info...\n");
    while (1)
    {
        bl_pack_send(bl, CMD_W_FW_INFO, 0, (uint8_t *)firmwareInfo, sizeof(firmware_info_t));
        int waitting = 0;
        while (1) // wait ack
        {
            // printf("send: [%D]\n", bl->pack.buffer_size);
            // for(int i = 0; i < bl->pack.buffer_size; i++)
            // {
            //     if(i % 32 == 0)
            //         printf("\n");
            //     printf("%02X ", bl->pack.buffer[i]);
            // }
            // delay_ms(20);
            int ret = bl_pack_recv(bl);
            if (ret == 0 && bl->pack.cmd1 == CMD_ACK)
            {
                printf("Done!\n\n");
                return 0;
            }
            else if (ret == 0 && bl->pack.cmd1 == CMD_NACK)
            {
                printf("nack! error:%d\n", bl->pack.data_size);
                break;
            }
            else
            {
                // printf("rev: 0x%08X, [%d]", ret, bl->pack.buffer_size);
                // for (int i = 0; i < bl->pack.buffer_size; i++)
                // {
                //     if (i % 32 == 0)
                //         printf("\n");
                //     printf("%02X ", bl->pack.buffer[i]);
                // }
                // printf("\n");
                if (waitting++ > 10)
                {
                    printf("wait ack on write fw info timeout!\n");
                    break;
                }
            }
        }
        if (sendtry++ >= 2)
        {
            printf("write fw info timeout! exit!\n");
            return -1;
        }
    }
    return 0;
}
int reset(bl_info_t *bl)
{
    uint32_t sendtry = 0;
    printf("\nResetting...\n");
    while (1)
    {
        bl_pack_send(bl, CMD_RESET, 0, NULL, 0);
        int waitting = 0;
        int ret = bl_pack_recv(bl);
        if (ret == 0 && bl->pack.cmd1 == CMD_ACK)
        {
            printf("Done!\n\n");
            return 0;
        }
        else if (ret == 0 && bl->pack.cmd1 == CMD_NACK)
        {
            printf("nack! error:%d\n", bl->pack.data_size);
        }
        else
        {
            if (waitting++ > 2)
            {
                printf("wait ack on reset timeout!\n");
                break;
            }
        }
        if (sendtry++ >= 2)
        {
            printf("reset timeout! exit!\n");
            return -1;
        }
    }
}
